/*
  EOMono
  Copyright 2007, Adam Tauno Williams (Whitemice Consulting)
  License: LGPLv2  < http://www.gnu.org/licenses/lgpl-2.1.html >
 */
using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;

namespace Whitemice.EOMono
{
  /// <summary>
  ///  Base class for all operations on SQL databases, this class provides
  ///  common utility methods for creating and populating SQL commands.
  /// </summary>
  public abstract class EOSQLCommand : EOCommand
  {
    protected EOEntity                  entity;           // entity to operate upon
    protected IEOSQLConnection          connection;       // connection to the db
    protected System.Data.IDbCommand    command;          // database command
    protected IList<IEORecord>          results;          // container for results
    protected bool                      doCommit = false; // enable/disable transactions
    
    public EOSQLCommand(IEOConnection connection, string entity)
      : this(connection, EOModel.Entity(entity))
    {
    } // end ctor
    
    public EOSQLCommand(IEOConnection connection, EOEntity entity) : base()
    {
      this.entity = entity;
      if (connection is IEOSQLConnection)
        this.connection = (connection as IEOSQLConnection);
      else throw new Exception("SQL command on non-SQL connection!");
      results = new List<IEORecord>();
    } // end ctor
    
    public EOEntity Entity
    { 
      get { return entity; } 
    } // end Entity
    
    [EOArguementAttribute("doCommit", "")]
    public bool DoCommit
    {
      set { doCommit = value; }
      get { return doCommit; }
    } // end Record
    
    public IList<IEORecord> Run(object[] args)
    {
      if(Initialize(args))
      {
        command = connection.Connection.CreateCommand();
        Execute();
        command.Dispose();
			  command = null;        
      }
      connection = null;
      entity = null;
      return results;
    } // end Run
    
    /// <summary>
    /// Legacy reader processor
    /// </summary>
    /// <param name="reader">
    /// A <see cref="IDataReader"/>
    /// </param>
    protected void ReadToResults(IDataReader reader)
    { 
      ReadToResults(reader, null); 
    } // end ReadToResults
    
    /// <summary>
    /// Reader processor
    /// </summary>
    /// <param name="reader">
    /// A <see cref="IDataReader"/>
    /// </param>
    /// <param name="receiver">
    /// A <see cref="EORecordReceiver"/>
    /// </param>
    protected void ReadToResults(IDataReader reader, EORecordReceiver receiver)
    {
      if (receiver == null)
        receiver = new EORecordReceiver(this.DefaultReceiver);
      while(reader.Read()) 
      {
        IEORecord record = new EOSQLRecord(connection, Entity, true);
        try 
        {
          for(int count = 0; count < reader.FieldCount; count++)
            record.Set(reader.GetName(count), reader.GetValue(count));
          // send record to designated receiver
          receiver(record);
        } catch (Exception ex)
          {
            EOManager.Log.Fatal("Exception in results reader.");
            EOManager.Log.Fatal(ex.Message);
            EOManager.Log.Fatal(ex.StackTrace);
            throw ex;
          }
      } // end while
    } // end ReadToResults
    
    /// <summary>
    /// Default record receiver;  simply adds results to the commands
    /// result list.  If another receiver is specified the result list
    /// returned upon command completetion may be empty.
    /// </summary>
    /// <param name="record">
    /// A <see cref="IEORecord"/>
    /// </param>
    public void DefaultReceiver(IEORecord record)
    {
      results.Add(record);
    } // end DefaultReceiver
    
    protected bool Initialize(object[] args)
    {
      try
      {
        DoArguements(args);
      } catch (Exception innerException)
        {
          throw new EOCommandArguementException(innerException);
        }
      return true;
    } // end Initialize
    
    protected string FormatValue(object v)
    {
      if (v is IEOValue)
        return (v as IEOValue).SQLValue();
      else if (v is System.String)
        return string.Format("'{0}'", v.ToString().Trim().Replace("\'", ""));
      else if (v is System.DateTime)
        return FormatValue(((DateTime)v).ToString("yyyy-MM-dd HH:mm:ss"));
      else if (v is System.DBNull)
        return "NULL";
      else if (v is IList<string>)
      {
        string text = string.Format("");
        foreach(string item in (IList<string>)v)
        {
          if (text.Length == 0)
            text = string.Format("\"{0}\"", text);
          else
            text = string.Format("{0},\"{1}\"", text, item);
        }
        return string.Format("({0})", text);
      }
      else if (v == null)
        return "NULL";
      return v.ToString();
    } // end FormatValue

    protected string FormatExpression(EOEntity e, string f, string q, object v)
    { 
      string expression = null;

      switch (q)
      {
        case "EQUALS":
          if (v == null)
            expression = string.Format("{0} IS NULL", f);
          else if (v is IList<string>)
            expression =  string.Format("{0} IN {1}", f, FormatValue(v));
          else
            expression =  string.Format("{0} = {1}", f, FormatValue(v));
         break;
        case "LIKE":
          if (e.KindOfField(f) != "String")
          {
            expression = FormatExpression(e, f, "EQUALS", FormatValue(v));
          } else 
            {
              expression = string.Format("{0} LIKE(\"{1}\")", f, FormatValue(v).Replace('*', '%'));
            }
         break;
      } // end switch
      if (expression == null)
        throw new Exception("Unknown expression in command");
      return expression;
    } // end FormatExpression
    
    /// <summary>
    /// Returns a qualifier for matching a record
    /// </summary>
    /// <param name="record">
    /// A <see cref="IEORecord"/>
    /// </param>
    /// <returns>
    /// A <see cref="System.String"/>
    /// </returns>
    protected string PrimaryKeyQualifier(IEORecord  record)
    { 
      return PrimaryKeyQualifier(record, null); 
    } // end PrimaryKeyQualifier
    
    protected string PrimaryKeyQualifier(IEORecord record, object magicValue)
    {
      bool first = true;
      string sql = string.Empty;
      object v;
      foreach (string key in record.PrimaryKeys)
      {
        // Use magic value if provided and field is a serial value
        if ((record.KindOfField(key) == "Serial") && (magicValue != null))
          v = magicValue;
        else
          v = record.Get(key);
        if (first)
        {
          sql = string.Format("{0} = {1}", key, FormatValue(v));
          first = false;
        } else 
          {
            sql = string.Format("{0} AND {1} = {2}", sql, key, FormatValue(v));
          }
      } // end foreach
      return sql;  
    } // end PrimaryKeyQualifier
  }
}
